#include <types.h>
#include <syscall.h>
#include <stdio.h>
#include <vfs.h>
#include <malloc.h>
#include <elf_util.h>
#include <string.h>
#include <cJSON.h>
#include <log.h>

static void *load_elf(slot_t process, char *code)
{
    struct elf_info_t *info = elf_get_info(code);
    if (!info)
    {
        printf("Failed to get elf info\n");
        return NULL;
    }

    void *entry = info->ehdr.e_entry;

    for (int i = 0; i < info->ehdr.e_phnum; i++)
    {
        Elf64_Phdr *phdr = &info->phdr[i];
        slot_t vmo = usys_vmo_create(phdr->p_memsz, VMO_DATA);
        usys_vmo_write(vmo, 0, code + phdr->p_offset, phdr->p_filesz);
        usys_vmo_map_to_process(vmo, process, phdr->p_vaddr, PROT_WRITE | PROT_READ | PROT_EXEC, 0);
        printf("[%d] load program [%#x:%d]\n", i, phdr->p_vaddr, phdr->p_memsz);
    }

    elf_free_info(info);
    return entry;
}

static bool_t read_elf(int fd, char *code, size_t size)
{
    size_t buf_size = 1024 * 15;
    const char *buf = (const char *)malloc(buf_size);

    for (size_t off = 0; off < size;)
    {
        u64_t read_count = vfs_read(fd, buf, buf_size);
        memcpy(code + off, buf, read_count);
        off += read_count;
    }
    free(buf);
    return TRUE;
}

static char *prepare_env(slot_t stack,
                         char *const argv[restrict],
                         char *const envp[restrict])
{
    cJSON *root = cJSON_CreateObject();

    int count = 0;

    for (count = 0; argv && argv[count]; count++)
        ;

    cJSON *arg = cJSON_CreateStringArray(argv, count);
    cJSON_AddItemToObject(root, "arg", arg);

    for (count = 0; envp && envp[count]; count++)
        ;
    cJSON *env = cJSON_CreateStringArray(envp, count);
    cJSON_AddItemToObject(root, "env", env);

    char *string = strdup(cJSON_PrintUnformatted(root));

    cJSON_Delete(root);
    return string;
}

int spawn(slot_t *new_process, const char *restrict path,
          slot_t slots[], int nr,
          char *const argv[restrict],
          char *const envp[restrict])
{
    slot_t process = usys_process_create();

    if (process < 0)
    {
        printf("%s: fail to create new_process_cap (ret: %d)\n", __func__, process);
    }

    int fd = vfs_open(path, O_RDONLY, 0);
    struct vfs_stat_t st;
    vfs_fstat(fd, &st);
    printf("fd:%d size:%ld\n", fd, st.st_size);

    const char *code = (const char *)malloc(st.st_size);
    read_elf(fd, code, st.st_size);
    void *entry = load_elf(process, code);
    free(code);
    vfs_close(fd);

    slot_t vmo_stack = usys_vmo_create(4096 * 16, VMO_DATA);
    char *env = prepare_env(vmo_stack, argv, envp);

    LOGD("env: " $(env));

    size_t len = strlen(env) + 1;

    usys_vmo_write(vmo_stack, 4096 * 16 - len, env, len);
    free(env);

    usys_vmo_map_to_process(vmo_stack, process, 0x80000000UL - 4096 * 16, PROT_WRITE | PROT_READ, 0);

    slot_t thread = usys_thread_create(process, 0x80000000UL - len, entry, NULL);

    return 0;
}